#include "Funcalyzer.h"
#include "Method.h"

#include<iostream>
#include<string>
#include<set>

using namespace std;

string& trim(string& in_rString)
{
    // Trim Both leading and trailing spaces  
    size_t startpos = in_rString.find_first_not_of(" \t"); 
    // Find the first character position after excluding leading blank spaces  
    size_t endpos = in_rString.find_last_not_of(" \t"); 
    // Find the first character position from reverse af  
       
    // if all spaces or empty return an empty string  
    if(( string::npos == startpos ) || ( string::npos == endpos))  
    {  
        in_rString = "";  
    }  
    else
    {
        in_rString = in_rString.substr(startpos, endpos-startpos+1 ); 
    }

    return in_rString;
}

void splitAsNameAndType(string& in_sString, string& in_sName, string& in_sType)
{
    size_t nLen;
    size_t nSplitPos;

    trim(in_sString);
    nSplitPos = in_sString.find_last_of("\t ");
    nLen = in_sString.size();

    if(string::npos == nSplitPos)
        in_sType = in_sString;
    else
    {
        in_sName = in_sString.substr(nSplitPos, nLen -nSplitPos);
        in_sType = in_sString.substr(0,nSplitPos);
    }

    trim(in_sName);
    trim(in_sType);
}

Funcalyzer::Funcalyzer(void)
{
	m_pxMethod = NULL;
    m_nDepthMax = 0;
    m_bReturnActive = false;
    m_bFuncCallActive = false;
}

Funcalyzer::~Funcalyzer(void)
{
}

void Funcalyzer::onNewToken(Token& inToken)
{
	Scope::Type eScope = m_pxCurrentScope->getScopeType();

    if(!m_pxMethod)
        return;

    char cDelimiter=char(inToken);
    switch(cDelimiter)
    {
    case '\n':
    case '\r':
        if( (eScope == Scope::eScopeCommentC) ||
			(eScope == Scope::eScopeCommentCPP))
            m_pxMethod->m_xComplexity.m_nLoCom++;
        else
            m_pxMethod->m_xComplexity.m_nLoC++;
        break;
    
    case ';':
        if( (eScope != Scope::eScopeCommentC) &&
            (eScope != Scope::eScopeCommentCPP))
            m_pxMethod->m_xComplexity.m_nStatements++;
    }

    // Call Information & Related Stuff
	// TODO: Incorrect code, rewrite

	if(eScope == Scope::eScopeCommentC) return;
	if(eScope == Scope::eScopeCommentCPP) return;

    if(isReturn(inToken.m_sValue))
	{
        m_bReturnActive = true;
		return;
	}

	if (m_bReturnActive)
    {
		if (0 == inToken.m_sValue.find_first_not_of("\t ("))
		{
			m_pxMethod->m_xReturn.m_asValues.push_back (inToken.m_sValue);
			m_bReturnActive = false;
		}
	}
    
    if ( !((eScope==(Scope::eScopeBlock))||
         (eScope==Scope::eScopeParen)))
		 return;

    if ((!isKeyword (inToken.m_sValue))&&
         (!inToken.m_bIsDelimiter))
	{
		m_sFuncCall = inToken.m_sValue;
        m_bFuncCallActive = true;
		return;
	}

    if (m_bFuncCallActive)
	{
        if (0 == inToken.m_sValue.find_first_not_of("\t ("))
            m_bFuncCallActive = false;
        else if (0 == inToken.m_sValue.compare("("))
            {
            m_pxMethod->m_asFunctionCalls.push_back(m_sFuncCall);
            m_bFuncCallActive = false;
            }
	}
}

void Funcalyzer::onScopeEntry(Scope& inNewScope, Scope& inOldScope)
{
    m_pxCurrentScope = &inNewScope;
    Scope::Type eNewScope = inNewScope.m_eType;

    switch(eNewScope)
    {
    case Scope::eScopeGlobal:
        break;

    case Scope::eScopeBlock:
        onEntryBlock(inOldScope);
        break;

    case Scope::eScopeParen:
        onEntryParen(inOldScope);
        break;

    case Scope::eScopeCommentC:
    case Scope::eScopeCommentCPP:
        onEntryComment(inOldScope);
        break;

    default:
        break;
    }
}

void Funcalyzer::onScopeExit(Scope& inNewScope, Scope& inOldScope)
{
    m_pxCurrentScope = &inNewScope;
    Scope::Type eNewScope = inNewScope.m_eType;

    switch(eNewScope)
    {
    case Scope::eScopeGlobal:
        onExitToGlobal(inOldScope);
        break;

    case Scope::eScopeBlock:
        onExitToBlock(inOldScope);
        break;

    default:
        break;
    }
}

void Funcalyzer::onExitToGlobal(Scope& inOldScope)
{
    Scope::Type eFromScope = inOldScope.m_eType;

    switch(eFromScope)
    {
    case Scope::eScopeParen:
        onGlobalFromParen(inOldScope);
        break;

    case Scope::eScopeBlock:
        m_apxMethod.push_back(m_pxMethod);
        m_pxMethod=NULL;
        m_nDepthMax = 0;
        break;

    default:
        break;
    }
}

void Funcalyzer::onExitToBlock(Scope& inOldScope)
{
    Scope::Type eFromScope = inOldScope.m_eType;

    switch(eFromScope)
    {
    case Scope::eScopeBlock:
        m_nDepthMax--;
        break;

    default:
        break;
    }

}

void Funcalyzer::onGlobalFromParen(Scope& inOldScope)
{
    std::string sArgument;
    std::string sArgumentName;
    std::string sArgumentType;
    Argument xArgument;
    size_t nFields = inOldScope.m_asSubstream.size();

    for(size_t nLooper=0;nLooper<nFields;nLooper++)
    {
        sArgument = inOldScope.m_asSubstream[nLooper];
        splitAsNameAndType(sArgument,sArgumentName,sArgumentType);

        xArgument.m_sName = trim(sArgumentName);
        xArgument.m_sTypePrefix = trim(sArgumentType);
        m_pxMethod->m_axArguments.push_back(xArgument);
    }
}

void Funcalyzer::onEntryParen(Scope& inOldScope)
{
    Scope::Type eFromScope = inOldScope.m_eType;
    std::string sMethod = inOldScope.m_asSubstream.back();
    std::string sMethodType;
    std::string sMethodName;

    switch(eFromScope)
    {
    case Scope::eScopeGlobal:
        m_pxMethod = new Method;
        splitAsNameAndType(sMethod,sMethodName,sMethodType);
        m_pxMethod->m_sName = sMethodName;
        m_pxMethod->m_xReturn.m_sType = sMethodType;
        break;

    default:
        break;
    }
}

void Funcalyzer::onEntryBlock(Scope& inOldScope)
{
    Scope::Type eFromScope = inOldScope.m_eType;

    switch(eFromScope)
    {
    case Scope::eScopeBlock:
        if(!m_pxMethod)
            break;
        m_pxMethod->m_xComplexity.m_nPaths++;
        m_nDepthMax++;
        if(m_nDepthMax > m_pxMethod->m_xComplexity.m_nMaxDepth)
            m_pxMethod->m_xComplexity.m_nMaxDepth = m_nDepthMax;
        break;

    default:
        break;
    }
}

void Funcalyzer::onEntryComment(Scope& inOldScope)
{
    Scope::Type eFromScope = inOldScope.m_eType;

    switch(eFromScope)
    {
	case Scope::eScopeInvalid: //To remove warning
    default:
        if(m_pxMethod)
            m_pxMethod->m_xComplexity.m_nLoCom++;
        break;
    }
}


bool Funcalyzer::isReturn (std::string s)
{
	static const char* asKeywords[] = {
		"return",
		"throw"
	};

	for (int i = 0; i < sizeof(asKeywords) / sizeof(char*); i++)
		if (string(asKeywords[i]) == s)
			return true;

	return false;
}

bool Funcalyzer::isKeyword (std::string s)
{
	//TODO: Change to map-based
	static const char* asKeywords[] = {
		"and",
		"and_eq",
		"asm",
		"auto",
		"bitand",
		"bitor",
		"bool",
		"break",
		"case",
		"catch",
		"char",
		"class",
		"compl",
		"const",
		"const_cast",
		"continue",
		"default",
		"delete",
		"do",
		"double",
		"dynamic_cast",
		"else",
		"enum",
		"explicit",
		"export",
		"extern",
		"false",
		"float",
		"for",
		"friend",
		"goto",
		"if",
		"inline",
		"int",
		"long",
		"mutable",
		"namespace",
		"new",
		"not",
		"not_eq",
		"operator",
		"or",
		"or_eq",
		"private",
		"protected",
		"public",
		"register",
		"reinterpret_cast",
		"return",
		"short",
		"signed",
		"sizeof",
		"static",
		"static_cast",
		"struct",
		"switch",
		"template",
		"this",
		"throw",
		"true",
		"try",
		"typedef",
		"typeid",
		"typename",
		"union",
		"unsigned",
		"using",
		"virtual",
		"void",
		"volatile",
		"wchar_t",
		"while",
		"xor",
		"xor_eq"
	};

	for (int i = 0; i < sizeof(asKeywords) / sizeof(char*); i++)
		if (string(asKeywords[i]) == s)
			return true;

	return false;
}

void Funcalyzer::outputReturns(SubmarineOptions* pxOpt)
{
    size_t nMethods;
	size_t nReturns;
    Method* pxMethod;

    nMethods = Funcalyzer::m_apxMethod.size();
    for(size_t nLooper=0; nLooper<nMethods; nLooper++)
    {
        pxMethod = Funcalyzer::m_apxMethod[nLooper];
        if(!pxMethod)
            //TODO: Remove this crash-fix
            continue;

		nReturns = pxMethod->m_xReturn.m_asValues.size();

        for(size_t i=0;i<nReturns;i++)
        {
            cout << pxMethod->m_sName << ",";
            cout << pxMethod->m_xReturn.m_asValues[i] << "\n";
        }
	}
}

void Funcalyzer::outputCalls(SubmarineOptions* pxOpt)
{
    Method* pxMethod;

    size_t nMethods = Funcalyzer::m_apxMethod.size();
    for(size_t nLooper=0;nLooper<nMethods;nLooper++)
    {
        pxMethod = m_apxMethod[nLooper];
        if(!pxMethod)
            //TODO: Remove this fix to prevent crashing
            continue;

        size_t nFuncCalls = pxMethod->m_asFunctionCalls.size();
        for(size_t i=0; i< nFuncCalls; i++)
        {
            cout << pxMethod->m_sName << ",";
            cout << pxMethod->m_asFunctionCalls[i] << "\n";
        }
	}
}

void Funcalyzer::outputComplexity(SubmarineOptions* pxOpt)
{
    size_t nMethods;
    size_t nArguments;
    Method* pxMethod;

	bool bBare		 = pxOpt->m_bBareSummary;
	bool bListOnly	 = pxOpt->m_bListOnly;
	bool bReport	 = pxOpt->m_bReport;
	bool bModeQuiet	 = pxOpt->m_bModeQuiet;

	if (!((true==bBare)||(true==bReport)||(true==bListOnly)))
	{
		cout<<"Name,Arguments,Paths,MaxDepth,Statements,LoC,Comments,ReturnType\n";
		cout<<endl;
	}

    nMethods = m_apxMethod.size();
    for(size_t nLooper=0;nLooper<nMethods;nLooper++)
    {
        pxMethod = m_apxMethod[nLooper];
        if(!pxMethod)
            //FIXME Hot-fix to prevent crashing
            continue;

		nArguments = pxMethod->m_axArguments.size();
		if(bReport) 
		{
			cout<< "Function:("<<nLooper+1<<")"<<endl;
			cout<< "Name:"<<pxMethod->m_sName<<endl;
			cout<< "Type:"<<pxMethod->m_xReturn.m_sType<<endl;
			cout<< "Arguments:"<<nArguments<<endl;
			for(size_t nArgLooper=0;nArgLooper<nArguments;nArgLooper++)
			{
				cout<<"\t(";
				cout<<pxMethod->m_axArguments[nArgLooper].m_sName<<":";
				cout<<pxMethod->m_axArguments[nArgLooper].m_sTypePrefix;
				cout<<")"<<endl;
			}

			cout<< "Complexity:"<<endl;
			cout<< "\tPaths="<<pxMethod->m_xComplexity.m_nPaths<<"\n";
			cout<< "\tMaxDepth="<<pxMethod->m_xComplexity.m_nMaxDepth<<"\n";
			cout<< "\tStatements="<<pxMethod->m_xComplexity.m_nStatements<<"\n";
			cout<< "\tLoC="<<pxMethod->m_xComplexity.m_nLoC<<"\n";
			cout<< "\tLoCom="<<pxMethod->m_xComplexity.m_nLoCom<<"\n";
			cout<< "Summary:";
		}

        cout<<pxMethod->m_sName;
        if(bListOnly)
		{
			cout<<endl;
			continue;
		}

		cout<<",";
		cout<<pxMethod->m_axArguments.size()<<",";
        cout<<pxMethod->m_xComplexity.m_nPaths<<",";
        cout<<pxMethod->m_xComplexity.m_nMaxDepth<<",";
        cout<<pxMethod->m_xComplexity.m_nStatements<<",";
        cout<<pxMethod->m_xComplexity.m_nLoC<<",";
        cout<<pxMethod->m_xComplexity.m_nLoCom<<",";
        cout<<pxMethod->m_xReturn.m_sType<<"\n";

		if(bReport)
			cout<<endl;
    }
}

void Funcalyzer::dump(SubmarineOptions* pxOpt)
{
	bool bCalls		 = pxOpt->m_bCalls;
	bool bReturns	 = pxOpt->m_bReturns;
	bool bComplexity = pxOpt->m_bComplexity;

	if( (bCalls==false) && (bReturns ==false))
		bComplexity = true;

	if(bComplexity)
		outputComplexity(pxOpt);
	else 
	{
		if(bCalls)
			outputCalls(pxOpt);
		else if(bReturns)
			outputReturns(pxOpt);
	}

}
